package org.dromara.service;

import com.jcraft.jsch.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.config.SshConfig;
import org.dromara.dto.DataInsertResponse;
import org.dromara.entity.DataRow;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.*;
import java.sql.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 服务层，基于JSch 0.1.55实现目录复制（仅检查当前文件夹是否有PNG文件）
 */
@Slf4j
@Service
public class QuestDbService {
    @Value("${questdb.username}")
    private String username;
    @Value("${questdb.password}")
    private String password;
    @Value("${questdb.table}")
    private String tableName;
    @Value("${questdb.schema}")
    private String schema;

    @Value("${ssh.username}")
    private String sshUserName;
    @Value("${ssh.password}")
    private String sshPassword;
    @Value("${ssh.port}")
    private Integer sshPort;
    @Value("${ssh.sftp.timeout}")
    private int sftpTimeout;
    @Value("${ssh.sftp.retry}")
    private int sftpRetry;
    @Value("${ssh.sftp.retry-interval}")
    private int retryInterval;
    @Value("${ssh.sftp.create-base-dir:false}")
    private boolean createBaseDir;
    @Value("${ssh.sftp.temp-dir:${java.io.tmpdir}/temp-sftp}")
    private String localTempDir;

    @Resource
    private SshConfig sshConfig;


    static {
        // 启用JSch详细日志
        JSch.setLogger(new JSchLogger());
    }

    /**
     * JSch日志适配器
     */
    private static class JSchLogger implements com.jcraft.jsch.Logger {
        @Override
        public boolean isEnabled(int level) {
            return true; // 输出所有级别日志
        }

        @Override
        public void log(int level, String message) {
            String prefix = switch (level) {
                case com.jcraft.jsch.Logger.DEBUG -> "[JSch-DEBUG]";
                case com.jcraft.jsch.Logger.INFO -> "[JSch-INFO]";
                case com.jcraft.jsch.Logger.WARN -> "[JSch-WARN]";
                case com.jcraft.jsch.Logger.ERROR -> "[JSch-ERROR]";
                case com.jcraft.jsch.Logger.FATAL -> "[JSch-FATAL]";
                default -> "[JSch]";
            };
            log.info("{} {}", prefix, message);
        }
    }


    /**
     * 执行数据插入操作的核心方法
     */
    public DataInsertResponse insertData(String basePath, String dateFolder,
                                         LocalDateTime startTime, LocalDateTime endTime,
                                         String projectId, String devId, Integer taskId,
                                         String questdbUrl, String targetFolder, String ipAddress) throws Exception {
        // 初始化临时目录
        initTempDirectory();

        DataInsertResponse response = new DataInsertResponse();
        response.setSuccess(false);
        ChannelSftp channel = null;
        Session session = null;

        try {
            log.info("=== 开始执行数据插入流程 ===");
            // 1. 建立SFTP连接
            log.info("准备建立SFTP连接: {}@{}:{}", sshUserName, ipAddress, sshPort);
            JSch jsch = new JSch();
            session = jsch.getSession(sshUserName, ipAddress, sshPort);
            session.setPassword(sshPassword);

            // 适配Windows OpenSSH的配置
            session.setConfig("StrictHostKeyChecking", "no");
            session.setConfig("PreferredAuthentications", "password");
            session.setConfig("Compression.s2c", "none");
            session.setConfig("Compression.c2s", "none");
            session.setTimeout(sftpTimeout);
            session.connect();
            log.info("SSH会话连接成功");

            // 打开SFTP通道
            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect(sftpTimeout);
            log.info("成功连接到远程服务器SFTP服务: {}", ipAddress);

            // 2. 处理基础目录
            String sftpBasePath = convertToRemotePath(basePath);
            log.info("转换后的远程基础目录路径: [{}]", sftpBasePath);

            if (createBaseDir) {
                log.info("自动创建基础目录（若不存在）: {}", sftpBasePath);
                createRemoteDirectory(sftpBasePath, channel);
                validateRemoteDirectory(sftpBasePath, "远程基础目录", channel);
            } else {
                log.info("验证远程基础目录: {}", sftpBasePath);
                validateRemoteDirectory(sftpBasePath, "远程基础目录", channel);
            }

            // 3. 构建路径
            String sftpDateFolder = joinRemotePaths(sftpBasePath, dateFolder);
            String sftpSourceFolder;

            log.info("=== 路径关系确认 ===");
            log.info("基础目录: {}", sftpBasePath);
            log.info("目标日期目录: {}", sftpDateFolder);

            // 4. 创建日期目录
            log.info("创建日期目录: {}", sftpDateFolder);
            createRemoteDirectory(sftpDateFolder, channel);
            validateDirectoryWritable(sftpDateFolder, channel);

            // 5. 确定源目录
            if (StringUtils.hasText(targetFolder)) {
                sftpSourceFolder = joinRemotePaths(sftpBasePath, targetFolder);
                log.info("使用指定的源目录: {}", sftpSourceFolder);
            } else {
                sftpSourceFolder = findLatestDateFolder(sftpBasePath, sftpDateFolder, channel);
                if (sftpSourceFolder == null) {
                    throw new IOException("未找到基础目录下的任何日期文件夹");
                }
                log.info("使用最新日期文件夹作为源目录: {}", sftpSourceFolder);
            }

            // 6. 验证源目录
            log.info("验证源目录: {}", sftpSourceFolder);
            validateRemoteDirectory(sftpSourceFolder, "数据来源目录", channel);

            // 7. 复制目录结构（仅检查当前文件夹是否有PNG文件）
            log.info("开始复制目录结构（仅包含当前文件夹有PNG的目录）: [{}] → [{}]", sftpSourceFolder, sftpDateFolder);
            copyDirectoryStructureWithCurrentFolderPngCheck(sftpSourceFolder, sftpDateFolder, channel);
            log.info("目录结构复制完成");

            // 8. 验证复制结果
            validateCopyResult(sftpDateFolder, channel);

            // 9. 处理子文件夹并插入数据库
            log.info("列出目标日期目录的子文件夹: {}", sftpDateFolder);
            List<String> subfolders = listSubfolders(sftpDateFolder, channel);
            log.info("成功获取子文件夹列表，共{}个", subfolders.size());

            log.info("生成时间戳列表: {} 至 {}", startTime, endTime);
            List<LocalDateTime> timestamps = generateTimestamps(startTime, endTime);
            log.info("时间戳列表生成完成，共{}个", timestamps.size());

            log.info("生成数据行: 时间戳{}个，子文件夹{}个", timestamps.size(), subfolders.size());
            List<DataRow> dataRows = prepareDataRows(timestamps, subfolders, projectId, devId, taskId);
            log.info("数据行生成完成，共{}行", dataRows.size());

            log.info("插入数据到QuestDB: {}行", dataRows.size());
            try (Connection conn = getConnection(questdbUrl)) {
                insertDataToQuestDB(conn, dataRows);
                response.setSuccess(true);
                response.setMessage("数据插入成功");
                response.setExpectedCount(dataRows.size());
            }

            log.info("=== 数据插入流程执行完成 ===");
        } catch (Exception e) {
            log.error("流程执行异常，详细信息:", e);
            throw new Exception("数据插入异常: " + e.getMessage(), e);
        } finally {
            // 关闭资源
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
                log.info("SFTP通道已关闭");
            }
            if (session != null && session.isConnected()) {
                session.disconnect();
                log.info("SSH会话已关闭");
            }
            // 清理临时文件
            cleanTempFiles();
        }
        return response;
    }


    // ------------------------------ 核心：本地临时文件中转传输 ------------------------------

    /**
     * 初始化临时目录
     */
    private void initTempDirectory() {
        File tempDir = new File(localTempDir);
        if (!tempDir.exists()) {
            if (tempDir.mkdirs()) {
                log.info("创建本地临时目录成功: {}", localTempDir);
            } else {
                log.error("创建本地临时目录失败: {}", localTempDir);
                throw new RuntimeException("无法创建临时目录，SFTP传输无法进行");
            }
        }
    }

    /**
     * 清理临时文件（增强版：支持重试和目录删除）
     */
    private void cleanTempFiles() {
        File tempDir = new File(localTempDir);
        if (!tempDir.exists() || !tempDir.isDirectory()) {
            log.debug("临时目录不存在或不是目录: {}", tempDir.getAbsolutePath());
            return;
        }

        File[] tempFiles = tempDir.listFiles();
        if (tempFiles == null) {
            log.debug("临时目录为空或无法列出内容: {}", tempDir.getAbsolutePath());
            return;
        }

        for (File file : tempFiles) {
            // 重试3次，间隔1秒
            deleteFileWithRetry(file, 3, 1000);
        }
    }

    /**
     * 带重试的文件删除（支持文件和目录）
     * @param file 待删除文件/目录
     * @param maxRetries 最大重试次数
     * @param intervalMs 重试间隔(毫秒)
     */
    private void deleteFileWithRetry(File file, int maxRetries, int intervalMs) {
        int retryCount = 0;
        while (retryCount < maxRetries) {
            if (deleteFileOrDir(file)) {
                log.debug("清理临时文件成功: {}", file.getAbsolutePath());
                return;
            }
            retryCount++;
            log.warn("清理临时文件失败（第{}次重试）: {}", retryCount, file.getAbsolutePath());
            try {
                Thread.sleep(intervalMs);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.warn("删除重试被中断", e);
                return;
            }
        }
        log.error("达到最大重试次数，清理临时文件失败: {}", file.getAbsolutePath());
    }

    /**
     * 删除文件或目录（递归删除目录内容）
     */
    private boolean deleteFileOrDir(File file) {
        if (file.isDirectory()) {
            // 先删除子文件/目录
            File[] children = file.listFiles();
            if (children != null) {
                for (File child : children) {
                    if (!deleteFileOrDir(child)) {
                        return false; // 子文件删除失败，返回false
                    }
                }
            }
        }
        // 删除文件或空目录
        return file.delete();
    }

    /**
     * 通过本地临时文件中转复制文件（规避JSch 0.1.55流处理bug）
     */
    private void copyFileViaTempWithRetry(String sourcePath, String targetPath, ChannelSftp channel)
            throws IOException, InterruptedException, SftpException {
        int retryCount = 0;
        while (retryCount < sftpRetry) {
            File tempFile = null;
            try {
                log.debug("尝试复制文件（第{}次）: {} → {}", retryCount + 1, sourcePath, targetPath);

                // 1. 创建本地临时文件
                String fileName = new File(sourcePath).getName();
                tempFile = new File(localTempDir, System.currentTimeMillis() + "_" + fileName);
                log.debug("创建本地临时文件: {}", tempFile.getAbsolutePath());

                // 2. 从远程下载到本地临时文件
                channel.get(sourcePath, tempFile.getAbsolutePath());
                log.debug("文件下载到本地临时文件完成: {}字节", tempFile.length());

                // 3. 从本地临时文件上传到远程目标路径（使用try-with-resources确保流关闭）
                try (FileInputStream fis = new FileInputStream(tempFile)) {
                    channel.put(fis, targetPath, ChannelSftp.OVERWRITE);
                }
                log.debug("文件从临时文件上传到目标路径完成");

                // 4. 验证文件大小
                validateFileSize(sourcePath, targetPath, channel);
                log.info("文件复制成功: {} → {}", sourcePath, targetPath);
                return;
            } catch (SftpException e) {
                retryCount++;
                log.error("第{}次复制文件失败（SFTP错误代码:{}）: {} → {}",
                        retryCount, e.id, sourcePath, targetPath, e);
                if (retryCount >= sftpRetry) {
                    throw new IOException("达到最大重试次数(" + sftpRetry + ")，复制失败", e);
                }
                Thread.sleep(retryInterval);
            } finally {
                // 无论成功失败，都删除临时文件
                if (tempFile != null && tempFile.exists()) {
                    deleteFileWithRetry(tempFile, 2, 500);
                }
            }
        }
    }

    /**
     * 验证源文件和目标文件大小一致
     */
    private void validateFileSize(String sourcePath, String targetPath, ChannelSftp channel) throws SftpException, IOException {
        SftpATTRS sourceAttrs = channel.stat(sourcePath);
        SftpATTRS targetAttrs = channel.stat(targetPath);

        if (sourceAttrs.getSize() != targetAttrs.getSize()) {
            throw new IOException("文件传输不完整: 源文件[" + sourceAttrs.getSize() +
                    "字节]与目标文件[" + targetAttrs.getSize() + "字节]大小不一致 → " + sourcePath);
        }
        log.debug("文件大小验证通过: {}（{}字节）", sourcePath, sourceAttrs.getSize());
    }


    // ------------------------------ 目录复制相关方法 ------------------------------

    /**
     * 复制目录结构（仅检查当前文件夹是否包含PNG文件）
     */
    private void copyDirectoryStructureWithCurrentFolderPngCheck(String sourceDir, String targetDir, ChannelSftp channel)
            throws SftpException, IOException, InterruptedException {
        Vector<ChannelSftp.LsEntry> entries = channel.ls(sourceDir);
        log.info("源目录【{}】下共包含{}个一级条目（含.和..）", sourceDir, entries.size());

        for (ChannelSftp.LsEntry entry : entries) {
            String entryName = entry.getFilename();
            if (entryName.equals(".") || entryName.equals("..")) {
                continue;
            }

            String sourceEntryPath = joinRemotePaths(sourceDir, entryName);
            String targetEntryPath = joinRemotePaths(targetDir, entryName);
            log.info("开始处理: {}", sourceEntryPath);

            SftpATTRS attrs = entry.getAttrs();
            if (attrs.isDir()) {
                // 仅检查当前文件夹（不递归）是否包含PNG文件
                if (hasPngFilesInCurrentFolder(sourceEntryPath, channel)) {
                    log.info("├─ 当前文件夹包含PNG文件，开始复制: {}", sourceEntryPath);
                    createRemoteDirectory(targetEntryPath, channel);
                    validateDirectoryWritable(targetEntryPath, channel);
                    copyDirectoryContent(sourceEntryPath, targetEntryPath, channel);
                    log.info("└─ 文件夹复制完成: {}", targetEntryPath);
                } else {
                    log.info("├─ 当前文件夹不包含PNG文件，跳过复制: {}", sourceEntryPath);
                }
            } else {
                // 复制所有文件，因为父目录已经通过检查
                validateFileReadable(sourceEntryPath, channel);
                copyFileViaTempWithRetry(sourceEntryPath, targetEntryPath, channel);
            }
        }
    }

    /**
     * 仅检查当前文件夹（不递归子目录）中是否包含PNG文件
     */
    private boolean hasPngFilesInCurrentFolder(String directory, ChannelSftp channel) throws SftpException {
        Vector<ChannelSftp.LsEntry> entries = channel.ls(directory);

        for (ChannelSftp.LsEntry entry : entries) {
            String entryName = entry.getFilename();
            if (entryName.equals(".") || entryName.equals("..")) {
                continue;
            }

            // 只检查文件，不检查子目录
            if (!entry.getAttrs().isDir() && isPngFile(entryName)) {
                log.debug("在当前文件夹{}中发现PNG文件: {}", directory, entryName);
                return true;
            }
        }

        return false;
    }

    /**
     * 检查文件名是否为PNG文件
     */
    private boolean isPngFile(String fileName) {
        if (fileName == null) {
            return false;
        }
        String lowerFileName = fileName.toLowerCase();
        return lowerFileName.endsWith(".png");
    }

    /**
     * 递归复制目录内容（复制所有子内容，因为父目录已通过检查）
     */
    private void copyDirectoryContent(String sourceDir, String targetDir, ChannelSftp channel)
            throws SftpException, IOException, InterruptedException {
        Vector<ChannelSftp.LsEntry> entries = channel.ls(sourceDir);

        for (ChannelSftp.LsEntry entry : entries) {
            String entryName = entry.getFilename();
            if (entryName.equals(".") || entryName.equals("..")) {
                continue;
            }

            String sourcePath = joinRemotePaths(sourceDir, entryName);
            String targetPath = joinRemotePaths(targetDir, entryName);
            SftpATTRS attrs = entry.getAttrs();

            if (attrs.isDir()) {
                createRemoteDirectory(targetPath, channel);
                validateDirectoryWritable(targetPath, channel);
                log.debug("  ├─ 创建子目录: {}", targetPath);
                copyDirectoryContent(sourcePath, targetPath, channel);
            } else {
                validateFileReadable(sourcePath, channel);
                copyFileViaTempWithRetry(sourcePath, targetPath, channel);
            }
        }
    }


    // ------------------------------ 路径处理与验证 ------------------------------

    private String convertToRemotePath(String inputPath) {
        if (inputPath == null || inputPath.trim().isEmpty()) {
            throw new IllegalArgumentException("输入路径不能为空");
        }
        String remotePath = inputPath.replace("\\", "/");
        if (!remotePath.startsWith("/")) {
            remotePath = "/" + remotePath;
        }
        return remotePath.replaceAll("/+", "/");
    }

    private String joinRemotePaths(String... parts) {
        if (parts == null || parts.length == 0) {
            return "";
        }
        List<String> processedParts = Stream.of(parts)
                .map(this::convertToRemotePath)
                .map(part -> part.replaceAll("^/+", ""))
                .map(part -> part.replaceAll("/+$", ""))
                .filter(part -> !part.isEmpty())
                .collect(Collectors.toList());

        if (processedParts.isEmpty()) {
            return "";
        }

        String joined = "/" + String.join("/", processedParts);
        return joined.replaceAll("/+", "/");
    }

    private void validateRemoteDirectory(String remotePath, String desc, ChannelSftp channel) throws SftpException, IOException {
        try {
            SftpATTRS attrs = channel.stat(remotePath);
            if (!attrs.isDir()) {
                throw new IOException(desc + "不是目录: " + remotePath);
            }
            log.info("{}验证通过: {}", desc, remotePath);
        } catch (SftpException e) {
            String errorMsg = desc + "不存在或无法访问（路径: " + remotePath + "），错误代码: " + e.id;
            switch (e.id) {
                case 2:  errorMsg += " → 路径不存在"; break;
                case 3:  errorMsg += " → 权限不足"; break;
                case 20: errorMsg += " → 不是目录"; break;
                default: errorMsg += " → 远程服务拒绝访问";
            }
            throw new IOException(errorMsg, e);
        }
    }

    private void validateDirectoryWritable(String dirPath, ChannelSftp channel) throws SftpException, IOException {
        try {
            SftpATTRS attrs = channel.stat(dirPath);
            if ((attrs.getPermissions() & 0222) == 0) {
                throw new IOException("目录无写入权限: " + dirPath + "，权限值: " + Integer.toOctalString(attrs.getPermissions()));
            }
            log.info("目录可写性验证通过: {}", dirPath);
        } catch (SftpException e) {
            throw new IOException("验证目录可写性失败: " + dirPath, e);
        }
    }

    private void validateFileReadable(String filePath, ChannelSftp channel) throws SftpException, IOException {
        SftpATTRS attrs = channel.stat(filePath);
        if (attrs.isDir()) {
            throw new IOException("路径是目录而非文件: " + filePath);
        }
        if ((attrs.getPermissions() & 0444) == 0) {
            throw new IOException("文件无读取权限: " + filePath + "，权限值: " + Integer.toOctalString(attrs.getPermissions()));
        }
        log.debug("文件可读性验证通过: {}", filePath);
    }

    private void createRemoteDirectory(String remotePath, ChannelSftp channel) throws SftpException {
        String[] pathParts = remotePath.split("/");
        StringBuilder currentPath = new StringBuilder();

        for (String part : pathParts) {
            if (part.isEmpty()) continue;
            currentPath.append("/").append(part);
            String tempPath = currentPath.toString();

            try {
                channel.stat(tempPath);
                log.debug("目录已存在: {}", tempPath);
            } catch (SftpException e) {
                if (e.id == 2) { // 路径不存在
                    channel.mkdir(tempPath);
                    channel.chmod(0755, tempPath);
                    log.debug("创建目录并设置权限: {}", tempPath);
                } else {
                    throw new SftpException(e.id, "创建目录失败: " + tempPath, e);
                }
            }
        }
    }


    // ------------------------------ 目录查询与验证 ------------------------------

    private List<String> listSubfolders(String directory, ChannelSftp channel) throws SftpException {
        List<String> subfolders = new ArrayList<>();
        Vector<ChannelSftp.LsEntry> entries = channel.ls(directory);

        for (ChannelSftp.LsEntry entry : entries) {
            String fileName = entry.getFilename();
            if (fileName.equals(".") || fileName.equals("..")) {
                continue;
            }
            if (entry.getAttrs().isDir()) {
                subfolders.add(fileName);
                log.debug("找到子文件夹: {}", fileName);
            }
        }

        if (subfolders.isEmpty()) {
            throw new SftpException(0, "远程目录下未找到子文件夹: " + directory);
        }
        return subfolders;
    }

    private String findLatestDateFolder(String basePath, String excludeFolder, ChannelSftp channel) throws SftpException {
        log.info("查找基础目录下的最新日期文件夹: {}", basePath);
        log.info("排除目录: {}", excludeFolder);

        Vector<ChannelSftp.LsEntry> entries = channel.ls(basePath);
        long latestMtime = -1;
        String latestFolder = null;

        for (ChannelSftp.LsEntry entry : entries) {
            String fileName = entry.getFilename();
            if (fileName.equals(".") || fileName.equals("..")) {
                continue;
            }

            String folderPath = joinRemotePaths(basePath, fileName);
            if (folderPath.equals(excludeFolder)) {
                log.debug("跳过当前要创建的目录: {}", folderPath);
                continue;
            }

            if (entry.getAttrs().isDir()) {
                long mtime = entry.getAttrs().getMTime();
                log.debug("检测到日期目录: {}，修改时间: {}", folderPath, mtime);
                if (mtime > latestMtime) {
                    latestMtime = mtime;
                    latestFolder = folderPath;
                }
            }
        }

        if (latestFolder == null) {
            log.warn("基础目录下未找到任何有效日期文件夹");
        } else {
            log.info("找到最新的日期文件夹: {}", latestFolder);
        }
        return latestFolder;
    }

    private void validateCopyResult(String targetPath, ChannelSftp channel) throws SftpException, IOException {
        Vector<ChannelSftp.LsEntry> entries = channel.ls(targetPath);
        int actualCount = 0;
        for (ChannelSftp.LsEntry entry : entries) {
            if (!entry.getFilename().equals(".") && !entry.getFilename().equals("..")) {
                actualCount++;
            }
        }
        if (actualCount == 0) {
            throw new IOException("复制结果验证失败：目标目录为空 -> " + targetPath);
        }
        log.info("复制结果验证通过：目标目录包含{}个条目", actualCount);
    }


    // ------------------------------ 数据库操作方法 ------------------------------

    private Connection getConnection(String dbUrl) throws SQLException {
        log.debug("获取数据库连接: {}", dbUrl);
        return DriverManager.getConnection(dbUrl, username, password);
    }

    private List<LocalDateTime> generateTimestamps(LocalDateTime startTime, LocalDateTime endTime) {
        List<LocalDateTime> timestamps = new ArrayList<>();
        LocalDateTime currentTime = startTime;
        while (!currentTime.isAfter(endTime)) {
            timestamps.add(currentTime);
            currentTime = currentTime.plusHours(1).plusMinutes(45).plusSeconds(32);
        }
        return timestamps;
    }

    private List<DataRow> prepareDataRows(List<LocalDateTime> timestamps, List<String> subfolders,
                                          String projectId, String devId, Integer taskId) {
        if (timestamps == null || subfolders == null || subfolders.isEmpty()) {
            log.error("时间戳或子文件夹列表不能为空");
            throw new IllegalArgumentException("时间戳或子文件夹列表不能为空");
        }

        List<DataRow> dataRows = new ArrayList<>();
        int sequence = 0;
        int folderIndex = 0;
        for (LocalDateTime timestamp : timestamps) {
            if (folderIndex >= subfolders.size()) break;

            String subfolder = subfolders.get(folderIndex);
            DataRow row = new DataRow(
                    timestamp, projectId, devId, "10", sequence++,
                    subfolder, 0, "success", taskId, "false", "00"
            );
            dataRows.add(row);
            folderIndex++;
        }
        return dataRows;
    }

    private void insertDataToQuestDB(Connection conn, List<DataRow> dataRows) throws SQLException {
        if (dataRows.isEmpty()) {
            log.warn("无数据行需要插入");
            return;
        }

        String sql = "INSERT INTO " + tableName + " (" +
                "ts, project_id, dev_id, type, sequence, file_name, " +
                "duration, status, task_id, delete_status, time_unit" +
                ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
        log.debug("执行插入SQL: {}", sql);

        boolean autoCommit = conn.getAutoCommit();
        try {
            conn.setAutoCommit(false);
            try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                for (DataRow row : dataRows) {
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
                    String ts = row.getTimestamp().format(formatter);
                    pstmt.setString(1, ts);
                    pstmt.setString(2, row.getProjectId());
                    pstmt.setString(3, row.getDevId());
                    pstmt.setString(4, row.getType());
                    pstmt.setInt(5, row.getSequence());
                    pstmt.setString(6, row.getFileName());
                    pstmt.setInt(7, row.getDuration());
                    pstmt.setString(8, row.getStatus());
                    pstmt.setInt(9, row.getTaskId());
                    pstmt.setString(10, row.getDeleteStatus());
                    pstmt.setString(11, row.getTimeUnit());
                    pstmt.addBatch();
                }
                int[] results = pstmt.executeBatch();
                log.debug("批量插入完成，影响行数: {}", results.length);
            }
            conn.commit();
            log.info("数据提交成功");
        } catch (SQLException e) {
            if (conn != null && !conn.isClosed()) {
                conn.rollback();
                log.error("数据插入失败，已回滚", e);
            }
            throw e;
        } finally {
            if (autoCommit) {
                conn.setAutoCommit(true);
            }
        }
    }

    private ChannelSftp createChannel(String sshUserName, String sshPassword, String sshHost, int sshPort) throws JSchException {
        log.debug("创建SFTP通道: {}@{}:{}", sshUserName, sshHost, sshPort);
        return sshConfig.createSftpChannel(sshHost, sshPort, sshUserName, sshPassword);
    }
}
